package com.cysoft.starter.repeat.core.validata.impl;

import com.cysoft.starter.repeat.core.model.RepeatInfo;
import com.cysoft.starter.repeat.core.validata.RepeatValidata;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static org.springframework.util.SerializationUtils.serialize;

/**
 * @author 玖零
 * @version V0.0.1
 * @project cysoft-spring-boot-starter-repeat
 * @package com.cysoft.starter.repeat.core.validata.impl
 * @date 2025-03-02 16:33
 * @desc
 */
public class RedisRepeatValidata implements RepeatValidata {

    private final StringRedisTemplate stringRedisTemplate;


    /**
     * 线程局部变量，为每个线程维护一个独立的 Map 用于缓存请求验证结果避免多线程之间的数据干扰
     */
    private static final ThreadLocal<Map<String, Boolean>> threadLocalCache = ThreadLocal.withInitial(HashMap::new);



    /**
     * 构造函数，初始化 RedisRepeatValidata 类的实例
     */
    public RedisRepeatValidata(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 实现重复验证
     *
     * @param repeatInfo 包含请求键和过期时间等信息的对象
     * @return 如果是重复请求返回 true，否则返回 false
     */
    @Override
    public boolean validataRepeat(RepeatInfo repeatInfo) {
        // 获取请求的唯一标识键
        String requestKey = repeatInfo.getRequestKey();
        // 从当前线程的 ThreadLocal 中获取缓存的 Map
        Map<String, Boolean> localCache = threadLocalCache.get();
        // 检查缓存中是否已经存在该请求的验证结果
        Boolean isRepeat = localCache.get(requestKey);
        if (isRepeat != null) {
            // 如果缓存中存在结果，直接返回
            return isRepeat;
        }

        try {
            // 根据请求的过期时间和时间单位获取 Redis 的过期设置
            Expiration expiration = getExpiration(repeatInfo.getExpireTime(), repeatInfo.getExpireTimeUnit());
            // 将 "1" 序列化为字节数组，作为 Redis 存储的值
            // 执行 Redis 的 SETNX 操作，尝试将请求键和值存入 Redis
            // 如果键不存在则存入并返回 true，否则返回 false
            Boolean result = stringRedisTemplate.execute((RedisCallback<Boolean>) (connection) ->
                    connection.set(requestKey.getBytes(), serialize("1"), expiration, RedisStringCommands.SetOption.SET_IF_ABSENT)
            );

            // 处理 Redis 操作结果
            isRepeat = result != null && result;
            // 将验证结果存入当前线程的缓存 Map 中
            localCache.put(requestKey, isRepeat);
            // 返回验证结果
            return isRepeat;
        } catch (Exception e) {
            // 捕获 Redis 操作过程中的异常并打印错误信息
            System.err.println("Redis 操作异常: " + e.getMessage());
            // 出现异常时默认返回非重复请求
            return false;
        } finally {
            // 手动清理 ThreadLocal 中的缓存，避免线程复用带来的数据残留问题
            threadLocalCache.remove();
        }
    }

    /**
     *
     * @param expireTime
     * @param timeUnit
     * @return
     */
    private Expiration getExpiration(long expireTime, TimeUnit timeUnit) {
        switch (timeUnit) {
            case NANOSECONDS:
                return Expiration.from(expireTime, TimeUnit.NANOSECONDS);
            case MICROSECONDS:
                return Expiration.from(expireTime, TimeUnit.MICROSECONDS);
            case MILLISECONDS:
                return Expiration.from(expireTime, TimeUnit.MILLISECONDS);
            case SECONDS:
                return Expiration.from(expireTime, TimeUnit.SECONDS);
            case MINUTES:
                return Expiration.from(expireTime, TimeUnit.MINUTES);
            case HOURS:
                return Expiration.from(expireTime, TimeUnit.HOURS);
            case DAYS:
                return Expiration.from(expireTime, TimeUnit.DAYS);
            default:
                return Expiration.from(expireTime, TimeUnit.SECONDS);
        }
    }



}
